const Router = require('koa-router');
const util = require('ethereumjs-util')
const Web3 = require('web3')
const Tx = require('ethereumjs-tx');
const FACTORY_ABI = require('../abis/factory.json')
const PAIR_ABI = require('../abis/pair.json')
const ROUTER_ABI = require('../abis/router.json')
const { MaxUint256 } = require('@ethersproject/constants')
const { parseAmount, formatAmount, accDiv, calculateGasMargin, accMul, toFixed } = require('./Format')

const chainId = "0x4" //rinkeby
var web3 = new Web3();
web3.setProvider(new Web3.providers.HttpProvider("https://rinkeby.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"));
const sushiRinkeby = "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506" // sushiRouter
const factory = "0xc35dadb65012ec5796536bd9864ed8773abc74c4" //工厂合约


const USDT = {
    contract: "0x55d398326f99059fF775485246999027B3197955",
    decimal: 18,
    symbol: 'USDT'
}

const WBNB = {
    contract: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
    decimal: 18,
    symbol: 'BNB'

}
const BUSD = {
    contract: "0xe9e7cea3dedca5984780bafc599bd69add087d56",
    decimal: 18,
    symbol: 'BUSD'
}

let router = new Router();

// 获取交易对合约
async function getPair(tokenA, tokenB){
    const contract = new web3.eth.Contract(
        FACTORY_ABI,
        factory
    );
    const lpContract = await contract.methods.getPair(tokenA, tokenB).call()
    return lpContract
}
async function handleApprove(token, fromAddr, privateKey){
    return new Promise(async (resolve, reject)=>{
        const contract = new web3.eth.Contract(
            PAIR_ABI,
            token
        );
        const allowance = await contract.methods.allowance(token, sushiRinkeby).call()
        if(allowance > 0){
            resolve()
        }
        else{
            console.log(MaxUint256.toString(),"MaxUint256");
        const gas = await contract.methods.approve(sushiRinkeby, MaxUint256.toString()).estimateGas({from: fromAddr, value: 0})
        const code = contract.methods.approve(sushiRinkeby, MaxUint256.toString()).encodeABI();
        const nonce = await web3.eth.getTransactionCount(fromAddr);
        const gasPrice = await web3.eth.getGasPrice();
        const rawTx = {
            "from": fromAddr,
            "to": token,
            "nonce": nonce,
            'gasPrice': Number(gasPrice),
            'gasLimit': calculateGasMargin(gas),
            "chainId": chainId,
            "value": 0,
            "data": code,
        };
        let tx = new Tx(rawTx);
        tx.sign(privateKey);
        let serializedTx = tx.serialize();
        web3.eth.sendSignedTransaction("0x" + serializedTx.toString("hex"))
        .on("transactionHash", (hash) => {
            console.log("授权hash",hash);
            resolve(hash)
        })
        .on("receipt", (receipt) => {
            poolObject.withdrawLoading = false
            poolObject.success()
            this.$notify({
              title: 'Withdrawal succeeded',
              type: 'success'
            });
          })
        .on("error", (err) => {
            console.log("授权报错",err);
            reject(err)
        }); 
        }
        
    })
}
// 获取价格
async function getPrice(pair,unit){
    const contract = new web3.eth.Contract(
        PAIR_ABI,
        pair
    );
    const reservePromise = contract.methods.getReserves().call()
    const token0Promise = contract.methods.token0().call()
    const token1Promise = contract.methods.token1().call()
    const [reserve, token0, token1] = await Promise.all([reservePromise, token0Promise, token1Promise])
    if(token0 == unit){
        const erc20 = new web3.eth.Contract(
            PAIR_ABI,
            token1
        );
        const decimals1 = await erc20.methods.decimals().call()
        return accDiv(formatAmount(reserve._reserve0, 18),formatAmount(reserve._reserve1, decimals1))  
    }else{
        const erc20 = new web3.eth.Contract(
            PAIR_ABI,
            token0
        );
        const decimals0 = await erc20.methods.decimals().call()
        return accDiv(formatAmount(reserve._reserve1, 18),formatAmount(reserve._reserve0, decimals0))
    } 
}

async function getReserves(pair){
    const contract = new web3.eth.Contract(
        PAIR_ABI,
        pair
    );
    const reservePromise = contract.methods.getReserves().call()
    const token0Promise = contract.methods.token0().call()
    const token1Promise = contract.methods.token1().call()
    const [reserve, token0, token1] = await Promise.all([reservePromise, token0Promise, token1Promise])
    return { reserve0: reserve._reserve0, reserve1: reserve._reserve1, token0, token1 }
}

async function getAmountOut(reserve0, reserve1, token0, token1, amountIn, tokenIn){
    const contract = new web3.eth.Contract(
        ROUTER_ABI,
        sushiRinkeby
    );
    let amountOut;
    if(token0.toLowerCase() == tokenIn.toLowerCase()){
        amountOut = await contract.methods.getAmountOut(amountIn, reserve0, reserve1).call()
    }else if(token1.toLowerCase() == tokenIn.toLowerCase()){
        amountOut = await contract.methods.getAmountOut(amountIn, reserve1, reserve0).call()
    }else{
        amountOut = 0
    }
    return amountOut;
}

async function swapExactTokensForTokens(amountIn, amountOutMin, path, fromAddr, privateKey){
    return new Promise(async (resolve, reject)=>{
        const myContract = new web3.eth.Contract(
            ROUTER_ABI,
            sushiRinkeby
        );
        console.log(amountIn.toString(), amountOutMin.toString(), path, fromAddr,"amountIn.toString(), amountOutMin.toString(), path, fromAddr");
        const gas = await myContract.methods.swapExactTokensForTokens(amountIn.toString(), amountOutMin.toString(), path, fromAddr, 1950523336).estimateGas({from: fromAddr, value: 0})
        console.log(gas);
        const code = myContract.methods.swapExactTokensForTokens(amountIn.toString(), amountOutMin.toString(), path, fromAddr, 1950523336).encodeABI();
        const nonce = await web3.eth.getTransactionCount(fromAddr);
        const gasPrice = await web3.eth.getGasPrice();
        const rawTx = {
            "from": fromAddr,
            "to": sushiRinkeby,
            "nonce": nonce,
            'gasPrice': Number(gasPrice),
            'gasLimit': calculateGasMargin(gas),
            "chainId": chainId,
            "value": 0,
            "data": code,
        };
        console.log(rawTx,"rawTx");
        let tx = new Tx(rawTx);
        tx.sign(privateKey);
        let serializedTx = tx.serialize();
        web3.eth.sendSignedTransaction("0x" + serializedTx.toString("hex"))
        .on("transactionHash", (hash) => {
            console.log("闪兑hash",hash);
            resolve(hash)
        })
        .on("error", (err) => {
            console.log("闪兑出错",err);
            reject(err)
        }); 
    })
}
router.post('/getPrice', async (ctx, next) => {
    const { contract, uint } = ctx.request.body;
    let myUint = BUSD
    if(uint == 'BNB'){
        myUint = WBNB
    }
    else if(uint == 'USDT'){
        myUint = USDT
    }else{
        myUint = BUSD
    }
    const lpContract = await getPair(contract,myUint.contract)
    if(lpContract == '0x0000000000000000000000000000000000000000'){
        ctx.body = {
            code: 0,
            price: "没有此交易对",
            status: false,
        };
        return;
    }else{
        let price = await getPrice(lpContract,myUint.contract);
        price = price + ' ' + myUint.symbol
        ctx.body = {
        code: 0,
        price: price,
        status: true,
        };
    }
    
  });
  

// 0xc7ad46e0b8a400bb3c915120d284aafba8fc4735 dai
// 0xc778417e063141139fce010982780140aa0cd5ab weth
router.post('/swap', async (ctx, next) => {
    // , to , 
    let { amountIn , tokenIn , tokenOut, slippage, privateKey} = ctx.request.body;
    const lpContract = await getPair(tokenIn, tokenOut)
    if(lpContract == '0x0000000000000000000000000000000000000000'){
        ctx.body = {
            code: 0,
            price: "没有此交易对",
            status: false,
        };
        return;
    }
    // deadline
    const { reserve0, reserve1, token0, token1 } = await getReserves(lpContract)
    const erc20 = new web3.eth.Contract(PAIR_ABI, tokenIn);
    const decimalsIn = await erc20.methods.decimals().call()
    amountIn = parseAmount(amountIn, decimalsIn) //格式化输入数量
    const amountOut = await getAmountOut(reserve0, reserve1, token0, token1, amountIn, tokenIn)
    const amountOutMin = toFixed(accDiv(accMul(amountOut, slippage), 100))
    const _privateKey = Buffer.from(privateKey,"hex");
    let address = util.bufferToHex(util.privateToAddress(_privateKey))
    address = web3.utils.toChecksumAddress(address);
    try{
        let hash = ""
        if(tokenIn.toLowerCase() != 'bnb' && tokenOut.toLowerCase() != 'bnb'){
            hash = await swapExactTokensForTokens(amountIn, amountOutMin, [tokenIn, tokenOut], address, _privateKey)
        }
        ctx.body = {
            code: 0,
            data: hash,
            status: true,
        };
    }catch(e){
        ctx.body = {
            code: 0,
            error: e,
            status: false,
        };
    }
});
module.exports = router;
